﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;

namespace ObjectDB.Library
{
	public static class Persister
	{
		private static readonly string[] _simpleTypes = new[] {
					"Boolean", "Byte", "SByte", "Char", "Decimal", "Double", "Single",
					"Int32", "UInt32", "Int64", "UInt64", "Int16", "UInt16", "String"
				};

		public static XElement Serialize(object obj)
		{
			var type = obj.GetType();
			if ((obj is Boolean) || (obj is Byte) || (obj is SByte) || (obj is Char) || (obj is Decimal) || (obj is Double) || (obj is Single) ||
				(obj is Int32) || (obj is UInt32) || (obj is Int64) || (obj is UInt64) || (obj is Int16) || (obj is UInt16) || (obj is String))
			{
				return new XElement(type.Name, obj);
			}
			var elem = new XElement(type.Name);
			foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
			{
				elem.Add(new XElement(MakeXmlFriendly(field.Name), Serialize(field.GetValue(obj))));
			}
			return elem;
		}

		public static object Deserialize(XElement elem)
		{
			if (_simpleTypes.Contains(elem.Name.LocalName))
			{
				return Convert.ChangeType(elem.Value, FindType(elem.Name.LocalName));
			}
			return Deserialize(elem, FindType(elem.Name.LocalName));
		}

		public static object Deserialize(XElement elem, Type type)
		{
			if (_simpleTypes.Contains(type.Name))
			{
				return Convert.ChangeType(elem.Value, type);
			}

			var obj = Activator.CreateInstance(type);
			foreach (var fieldElem in elem.Elements())
			{
				var field = type.GetField(MakeCodeFriendly(fieldElem.Name.LocalName), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				field.SetValue(obj, Deserialize(fieldElem.Elements().First()));
			}
			return obj;
		}

		private static string MakeXmlFriendly(string name)
		{
			return name.Replace("<", "_lt_").Replace(">", "_gt_");
		}

		private static string MakeCodeFriendly(string name)
		{
			return name.Replace("_lt_", "<").Replace("_gt_", ">");
		}

		private static Type FindType(string typeName)
		{
			return AppDomain.CurrentDomain.GetAssemblies()
				.Where(asm => !asm.FullName.StartsWith("Microsoft.") && !asm.FullName.StartsWith("System."))
				.Select(asm => asm.GetTypes())
				.Aggregate(new List<Type>(), (list, types) => { list.AddRange(types); return list; })
				.Where(t => t.Name == typeName)
				.FirstOrDefault();
		}
	}
}